package com.gitee.fastmybatis.core.mapper;

import com.gitee.fastmybatis.core.util.ListUtil;
import org.apache.ibatis.annotations.Param;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;

/**
 * 具备保存功能的Mapper
 *
 * @param <E>实体类
 * @author tanghc
 */
public interface SaveMapper<E> extends Mapper<E> {
    /**
     * 保存，保存所有字段
     *
     * <pre>
     * 获取保存后的自增主键id值：<code>Integer id = entity.getId();</code>
     * </pre>
     *
     * @param entity 实体类
     * @return 受影响行数
     */
    int save(E entity);

    /**
     * 保存，忽略null字段
     *
     * <pre>
     * 获取保存后的自增主键id值：<code>Integer id = entity.getId();</code>
     * </pre>
     *
     * @param entity 实体类
     * @return 受影响行数
     */
    int saveIgnoreNull(E entity);

    /**
     * 批量保存
     * <pre>
     * 获取保存后的自增主键id值：<code>Integer id = entity.getId();</code>
     * 采用<code>INSERT INTO table(col1, col2,...) VALUES (val1, val2,...),(val1, val2,...),(val1, val2,...)</code>方式批量插入
     * </pre>
     * @param records 实体类集合
     * @return 受影响行数
     */
    int saveBatch(@Param("entitys") Collection<E> records);

    /**
     * 批量保存，忽略null字段<br>
     * null字段判断方式：取集合中第一条对象判断为null的字段
     * <pre>
     * 获取保存后的自增主键id值：<code>Integer id = entity.getId();</code>
     * 采用<code>INSERT INTO table(col1, col2,...) VALUES (val1, val2,...),(val1, val2,...),(val1, val2,...)</code>方式批量插入
     * </pre>
     * @param records 实体类集合
     * @return 受影响行数
     */
    default int saveBatchIgnoreNull(Collection<E> records) {
        if (records == null || records.isEmpty()) {
            return 0;
        }
        List<String> notNullColumns = ListUtil.getNotNullFieldNames(records);
        return saveBatchBySpecifiedColumns(records, notNullColumns);
    }

    /**
     * 批量保存，忽略null字段<br>
     * null字段判断方式：取集合中第一条对象判断为null的字段
     * <pre>
     * 获取保存后的自增主键id值：<code>Integer id = entity.getId();</code>
     * 采用<code>INSERT INTO table(col1, col2,...) VALUES (val1, val2,...),(val1, val2,...),(val1, val2,...)</code>方式批量插入
     * </pre>
     * @param records 实体类集合
     * @param partitionSize 每次保存行数
     * @return 受影响行数
     */
    default int saveBatchIgnoreNull(Collection<E> records, int partitionSize) {
        if (records == null || records.isEmpty()) {
            return 0;
        }
        if (partitionSize < 1) {
            throw new IllegalArgumentException("参数 partitionSize 必须大于 0");
        }
        int successCount = 0;
        List<E> list = records instanceof List ? (List<E>) records : new ArrayList<>(records);
        List<List<E>> partition = ListUtil.partition(list, partitionSize);
        for (List<E> subList : partition) {
            List<String> notNullColumns = ListUtil.getNotNullFieldNames(subList);
            int i = saveBatchBySpecifiedColumns(subList, notNullColumns);
            successCount += i;
        }
        return successCount;

    }

    /**
     * 批量保存，指定保存字段<br>
     * <pre>
     * 获取保存后的自增主键id值：<code>Integer id = entity.getId();</code>
     * 采用<code>INSERT INTO table(col1, col2,...) VALUES (val1, val2,...),(val1, val2,...),(val1, val2,...)</code>方式批量插入
     * </pre>
     * @param records 实体类集合
     * @param saveFieldNames 指定保存哪些字段，可以是实体类属性名，也可以是数据库字段名
     * @return 受影响行数
     */
    int saveBatchBySpecifiedColumns(@Param("entitys") Collection<E> records, @Param("saveFieldNames") List<String> saveFieldNames);

    /**
     * 分批次保存<br>
     * <pre>
     * <code>saveBatch(records, 500)</code>
     * 分批次保存，每次保存500条
     * 采用<code>INSERT INTO table(col1, col2,...) VALUES (val1, val2,...),(val1, val2,...),(val1, val2,...)</code>方式批量插入
     * </pre>
     * <pre>
     * 获取保存后的自增主键id值：<code>Integer id = entity.getId();</code>
     * </pre>
     * @param records 实体类集合
     * @param partitionSize 每次保存行数
     * @return 返回保存成功行数
     */
    default int saveBatch(Collection<E> records, int partitionSize) {
        if (records == null || records.isEmpty()) {
            return 0;
        }
        if (partitionSize < 1) {
            throw new IllegalArgumentException("参数 partitionSize 必须大于 0");
        }
        int successCount = 0;
        List<E> list = records instanceof List ? (List<E>) records : new ArrayList<>(records);
        List<List<E>> partition = ListUtil.partition(list, partitionSize);
        for (List<E> subList : partition) {
            int i = this.saveBatch(subList);
            successCount += i;
        }
        return successCount;
    }

    /**
     * 批量保存,忽略重复行.<br>
     * 此方式采用union的方式批量insert.
     *
     * @param records 实体类集合
     * @return 受影响行数
     * @see #saveIgnoreNull(Object)
     * @see #saveUnique(Collection, Comparator)
     */
    int saveMultiSet(@Param("entitys") Collection<E> records);

    /**
     * 批量保存，去除重复行，通过对象是否相对判断重复数据，实体类需要实现equals方法.<br>
     * <pre>
     * 获取保存后的自增主键id值：<code>Integer id = entity.getId();</code>
     * </pre>
     * @param records 实体类集合，需要实现equals方法
     * @return 受影响行数
     */
    default int saveUnique(Collection<E> records) {
        return saveUnique(records, null);
    }

    /**
     * 批量保存，去除重复行，指定比较器判断<br>
     * <pre>
     * 获取保存后的自增主键id值：<code>Integer id = entity.getId();</code>
     * </pre>
     * @param records 实体类集合，需要实现equals方法
     * @param comparator 对象比较器
     * @return 受影响行数
     */
    default int saveUnique(Collection<E> records, Comparator<E> comparator) {
        if (records == null || records.isEmpty()) {
            return 0;
        }
        if (comparator == null) {
            return saveBatch(new HashSet<>(records));
        }
        List<E> list = new ArrayList<>(records);
        for (int i = 0; i < list.size() - 1; i++) {
            E obj1 = list.get(i);
            for (int j = list.size() - 1; j > i; j--) {
                E obj2 = list.get(j);
                if (comparator.compare(obj1, obj2) == 0) {
                    list.remove(j);
                }
            }
        }
        return saveBatch(list);
    }
}
